import { Application } from 'egg'
import { GraphQLClient } from 'graphql-request'
import type {
  RequestDocument,
  Variables
} from 'graphql-request/dist/types'

import { SignJWT } from 'jose/jwt/sign'
import { jwtVerify } from 'jose/jwt/verify'

const GQL_REQEUST = Symbol('Application#gqlRequest')
const JWT = Symbol('Application#jwt')

export default {
  get gqlRequest() {
    const app: Application = this as any

    if (!app[GQL_REQEUST]) {
      const {
        HASURA_ENDPOINT,
        HASURA_GRAPHQL_ADMIN_SECRET
      } = app.config['graphql']
      const client = new GraphQLClient(
        HASURA_ENDPOINT, {
          headers: {
            'x-hasura-admin-secret': HASURA_GRAPHQL_ADMIN_SECRET
          }
        }
      )

      app[GQL_REQEUST] = async function<T extends unknown>(
        query: RequestDocument,
        variables?: Variables
      ): Promise<T> {
        try {
          return await client.request(query, variables) as T
        } catch (err) {
          app.logger.error(err)
        }
      }
    }

    return app[GQL_REQEUST]
  },

  get jwt() {
    const app: Application = this as any

    if (!app[JWT]) {
      const {
        issuer,
        expiresIn,
        raw: {
          privateKey,
          publicKey
        }
      } = app.config['jwt']

      app[JWT] = {
        sign: async (payload, expires = expiresIn) => {
          try {
            return await new SignJWT(payload)
              .setIssuedAt()
              .setSubject(payload.id)
              .setProtectedHeader({ alg: 'RS256' })
              .setIssuer(issuer)
              .setExpirationTime(expires)
              .sign(privateKey)
          } catch (err) {
            app.logger.error(err)
          }
        },
        verify: async token => {
          if (!token) return

          try {
            const { payload } = await jwtVerify(token, publicKey, { issuer })

            delete payload['https://hasura.io/jwt/claims']
            delete payload['iat']
            delete payload['iss']
            delete payload['exp']

            return payload
          } catch (err) {
            app.logger.info(err.code ? err.code : err)
          }
        }
      }
    }

    return app[JWT]
  }
}
